package cn.felord.security.autoconfigure.authentication.miniapp;


import cn.felord.security.autoconfigure.handler.SecurityRest;
import cn.felord.security.autoconfigure.handler.SecurityRestBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.felord.security.autoconfigure.handler.ResponseWriter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.BufferedReader;
import java.io.IOException;

/**
 * 小程序会话
 *
 * @author n1
 * @since 2021 /6/25 10:44
 */
public abstract class AbstractMiniAppSessionAuthenticationFilter extends OncePerRequestFilter {
    private static final String ATTRIBUTE_KEY = "miniappAuth";
    private final MiniAppSessionService miniAppSessionService;
    private final ObjectMapper om = new ObjectMapper();

    private final RequestMatcher requiresAuthenticationRequestMatcher;
    private final Converter<HttpServletRequest, MiniAppSessionRequest> sessionRequestConverter;

    private final PreAuthResponseWriter preAuthResponseWriter = new PreAuthResponseWriter();

    /**
     * Instantiates a new Abstract mini app session authentication filter.
     *
     * @param miniAppSessionService miniAppSessionService
     * @param requestMatcher        requestMatcher
     */
    public AbstractMiniAppSessionAuthenticationFilter(MiniAppSessionService miniAppSessionService,
                                                      RequestMatcher requestMatcher) {
        this.miniAppSessionService = miniAppSessionService;
        this.requiresAuthenticationRequestMatcher = requestMatcher;
        this.sessionRequestConverter = this.defaultConverter();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (response.isCommitted()) {
            return;
        }
        if (requiresAuthenticationRequestMatcher.matches(request)) {
            MiniAppSessionRequest appSessionRequest = this.sessionRequestConverter.convert(request);
            MiniAppSessionRequest sessionRequest = this.enhance(appSessionRequest);
            MiniAppSessionResponse sessionResponse = miniAppSessionService.jscode2Session(sessionRequest);
            request.setAttribute(ATTRIBUTE_KEY, sessionResponse);
            preAuthResponseWriter.write(request, response);
            return;
        }
        filterChain.doFilter(request, response);
    }
    private Converter<HttpServletRequest, MiniAppSessionRequest> defaultConverter() {
        return request -> {
            try (BufferedReader reader = request.getReader()) {
                MiniAppSessionRequest miniAppRequest = this.om.readValue(reader, MiniAppSessionRequest.class);
                if (!StringUtils.hasText(miniAppRequest.getJsCode())) {
                    throw new BadCredentialsException("code is required");
                }
                return miniAppRequest;
            } catch (IOException e) {
                throw new BadCredentialsException("cannot obtain session request body");
            }
        };
    }

    /**
     * Resolver mini app session request.
     *
     * @param appSessionRequest the app session request
     * @return the mini app session request
     */
    protected abstract MiniAppSessionRequest enhance(MiniAppSessionRequest appSessionRequest);

    private static class PreAuthResponseWriter extends ResponseWriter {

        @Override
        protected SecurityRest<?> body(HttpServletRequest request) {
            MiniAppSessionResponse sessionResponse = (MiniAppSessionResponse) request.getAttribute(ATTRIBUTE_KEY);
            return SecurityRestBody.okData(sessionResponse, HttpStatus.OK.getReasonPhrase());
        }
    }

}
